package ace.module.common.core.impl.manager.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.manager.spring.AbstractSpringDtoManager;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.spring.data.jdbc.querydsl.core.QuerydslRelationHelper;
import ace.cmp.spring.data.jdbc.querydsl.core.util.DeleteFlagUtils;
import ace.module.common.api.constant.CommonConstants;
import ace.module.common.core.impl.annotation.appout.AppStatus;
import ace.module.common.core.impl.annotation.appout.OutId;
import ace.module.common.core.impl.annotation.appout.OutType;
import ace.module.common.core.impl.manager.ApplicationManager;
import ace.module.common.core.impl.manager.helper.ManagerHelper;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.sql.RelationalPathBase;
import java.io.Serializable;
import java.lang.reflect.Field;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.SneakyThrows;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;

/**
 * 应用接口.
 *
 * @author Caspar
 */
public class AbstractApplicationManager<EntityT, IdT, EntityDtoT>
    extends AbstractSpringDtoManager<EntityT, IdT, EntityDtoT>
    implements ApplicationManager<EntityT, IdT, EntityDtoT>, InitializingBean {

  private static final String DEFAULT_OUT_ID_FIELD_NAME = "outId";
  private static final String DEFAULT_OUT_TYPE_FIELD_NAME = "outType";
  private static final String DEFAULT_STATUS_FIELD_NAME = "status";

  /**
   * 正常状态.
   */
  private Integer statusNormal = CommonConstants.STATUS_NORMAL;
  @Autowired
  @Getter(AccessLevel.PROTECTED)
  private MessageSourceAssertService assertService;

  @Autowired
  @Getter(AccessLevel.PROTECTED)
  private ManagerHelper managerHelper;

  @Autowired
  @Getter(AccessLevel.PROTECTED)
  private QuerydslRelationHelper querydslRelationHelper;

  @Getter(AccessLevel.PROTECTED)
  private RelationalPathBase<EntityT> entityPath;


  @Getter(AccessLevel.PROTECTED)
  private AppStatus appStatusAnn;

  @Getter(AccessLevel.PROTECTED)
  private Field outTypeField;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression outTypeExpression;

  @Getter(AccessLevel.PROTECTED)
  private Field outIdField;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression outIdExpression;

  @Getter(AccessLevel.PROTECTED)
  private Field deleteFlagField;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression deleteFlagExpression;

  @Getter(AccessLevel.PROTECTED)
  private Field statusField;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression statusExpression;


  @Override
  public void afterPropertiesSet() throws Exception {
    super.afterPropertiesSet();
    this.entityPath = this.querydslRelationHelper.getRelationalPathBaseByEntityType(this.getEntityClass());
    this.outTypeField = this.managerHelper.findFieldByAnnClassOrFieldName(this.getEntityClass(), OutType.class, DEFAULT_OUT_TYPE_FIELD_NAME);
    this.outTypeExpression = this.managerHelper.findExpressionByAnnClassOrFieldName(this.getEntityClass(), OutType.class, this.outTypeField.getName());

    this.outIdField = this.managerHelper.findFieldByAnnClassOrFieldName(this.getEntityClass(), OutId.class, DEFAULT_OUT_ID_FIELD_NAME);
    this.outIdExpression = this.managerHelper.findExpressionByAnnClassOrFieldName(this.getEntityClass(), OutId.class, this.outIdField.getName());

    this.statusField = this.managerHelper.findFieldByAnnClassOrFieldName(this.getEntityClass(), AppStatus.class, DEFAULT_STATUS_FIELD_NAME);
    this.statusExpression = this.managerHelper.findExpressionByAnnClassOrFieldName(this.getEntityClass(), AppStatus.class, this.statusField.getName());

    this.deleteFlagField = this.querydslRelationHelper.getLogicDeleteField(this.getEntityClass());
    this.deleteFlagExpression = this.querydslRelationHelper.getLogicDeleteExpression(this.getEntityClass());

    this.appStatusAnn = AnnotationUtils.findAnnotation(this.statusField, AppStatus.class);
  }

  @Override
  public EntityT getByOutIdAndOutType(String outId, String outType) {
    return this.getRepository().query(sqlQuery -> sqlQuery
        .select(this.getEntityPath())
        .from(this.getEntityPath())
        .where(
            this.getOutIdExpression().eq(outId),
            this.getOutTypeExpression().eq(outType),
            this.getDeleteFlagExpression().eq(CoreConstant.DELETE_FLAG_NOT)
        )
        .fetchOne()
    );
  }

  @Override
  public EntityT getAndCheckByOutIdAndOutType(String outId, String outType) {
    EntityT entity = this.getByOutIdAndOutType(outId, outType);
    this.checkAll(entity);
    return entity;
  }

  @Override
  public <AppIdT extends Serializable> AppIdT getIdByOutIdAndOutType(String outId, String outType) {
    EntityT entity = this.getByOutIdAndOutType(outId, outType);
    return (AppIdT) this.getEntityId(entity);
  }

  @Override
  public <AppIdT extends Serializable> AppIdT getIdAndCheckByOutIdAndOutType(String outId, String outType) {
    EntityT entity = this.getByOutIdAndOutType(outId, outType);
    this.checkAll(entity);
    return (AppIdT) this.getEntityId(entity);
  }

  @Override
  public void checkAll(EntityT entity) {
    assertService.isNull(
        entity,
        this.getEntityClass().getName() + ".is-null",
        new Object[]{},
        "应用数据不存在");

    assertService.isTrue(
        DeleteFlagUtils.isDelete(entity),
        this.getEntityClass().getName() + ".is-deleted",
        new Object[]{},
        "应用数据不存在");

    Object statusValue = this.getStatusValue(entity);

    if (statusValue != null) {
      Integer anotherStatusValue = this.getAppStatusAnn() != null ? this.getAppStatusAnn().normal() : this.statusNormal;
      assertService.isFalse(
          statusValue.equals(anotherStatusValue),
          this.getEntityClass().getName() + ".is-locked",
          new Object[]{},
          "应用已锁定");
    }
  }

  @SneakyThrows
  private Object getStatusValue(EntityT entity) {
    if (entity == null) {
      return null;
    }
    return this.getStatusField().get(entity);
  }
}
